#include "usbh_hub_core.h"
#include "usbh_pipe.h"
#include "drv_usb_hw.h"

//extern usbh_host hUSBHost[5];
usbh_host hUSBHost[5];
usb_core_driver hUSBCore[5];
//extern HCD_HandleTypeDef _hHCD[2];

static usbh_status get_hub_descriptor(usbh_host *phost);
static usbh_status set_hub_port_power(usbh_host *phost, uint8_t hub_port);
static usbh_status hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t dataDirection, uint8_t porta, uint8_t *buffer, uint16_t size);
static usbh_status set_hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t port);
static usbh_status get_hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t porta, uint8_t *buffer, uint16_t size);
static usbh_status clear_port_feature(usbh_host *phost, uint8_t feature, uint8_t port);
static usbh_status set_port_feature(usbh_host *phost, uint8_t feature, uint8_t port);
static void clear_port_changed(uint8_t port);
static uint8_t get_port_changed();
static uint8_t port_changed(uint8_t *b);
static void detach(usbh_host *phost, uint16_t idx);
static void attach(usbh_host *phost, uint16_t idx, uint8_t lowspeed);
static void debug_port(uint8_t *buff, __IO USB_HUB_PORT_STATUS *info);

static usbh_status USBH_HUB_InterfaceInit  (usbh_host *phost);
static void USBH_HUB_InterfaceDeInit  (usbh_host *phost);
static usbh_status USBH_HUB_ClassRequest(usbh_host *phost);
static usbh_status USBH_HUB_Process(usbh_host *phost);
static usbh_status USBH_HUB_SOFProcess(usbh_host *phost);


static void usbh_hub_parse(USB_HUB_DESCRIPTOR *printer_desc, uint8_t *buf);

usbh_class  HUB_Class =
{
	USB_HUB_CLASS,
	USBH_HUB_InterfaceInit,
	USBH_HUB_InterfaceDeInit,
	USBH_HUB_ClassRequest,
	USBH_HUB_Process,
	USBH_HUB_SOFProcess,
	NULL
};

static __IO USB_PORT_CHANGE HUB_Change;
static __IO uint8_t HUB_CurPort = 0;
static __IO USB_HUB_PORT_STATUS *HUB_ChangeInfo = 0;

static uint8_t  HUB_NumPorts = 0;
static uint16_t HUB_PwrGood  = 0;

static usbh_status USBH_HUB_InterfaceInit (usbh_host *phost) {
	uint8_t interface;
	usbh_status status = USBH_FAIL;
	static HUB_HandleTypeDef *HUB_Handle;
	
	HUB_NumPorts = 0;
	HUB_PwrGood = 0;
	HUB_ChangeInfo = 0;
	HUB_CurPort = 0;
	
	interface = usbh_interface_find(&phost->dev_prop, phost->active_class->class_code, 0x00, 0x00);
	if (0xFFU == interface) {
    phost->usr_cb->dev_not_supported();
		status = USBH_FAIL;
  } else {
		usbh_interface_select(&phost->dev_prop, interface);
		phost->USBH_ClassTypeDef_pData[0] = (HUB_HandleTypeDef *)calloc(1,sizeof(HUB_HandleTypeDef));
		HUB_Handle = phost->USBH_ClassTypeDef_pData[0];
		phost->interfaces = 1;
		
		HUB_Handle->state     = HUB_IDLE;
	  HUB_Handle->ctl_state = HUB_REQ_IDLE;
	  HUB_Handle->ep_addr   = phost->dev_prop.cfg_desc_set.itf_desc_set[phost->dev_prop.cur_itf]->ep_desc[0].bEndpointAddress;
	  HUB_Handle->length    = phost->dev_prop.cfg_desc_set.itf_desc_set[phost->dev_prop.cur_itf]->ep_desc[0].wMaxPacketSize;
	  HUB_Handle->poll      = phost->dev_prop.cfg_desc_set.itf_desc_set[phost->dev_prop.cur_itf]->ep_desc[0].bInterval;
		if (HUB_Handle->poll  < HUB_MIN_POLL)
	    HUB_Handle->poll = HUB_MIN_POLL;
			printf("device POLL %d, LEN %d", HUB_Handle->poll, HUB_Handle->length);
		if (phost->dev_prop.cfg_desc_set.itf_desc_set[phost->dev_prop.cur_itf]->ep_desc[0].bEndpointAddress & 0x80) {
			HUB_Handle->InEp = phost->dev_prop.cfg_desc_set.itf_desc_set[phost->dev_prop.cur_itf]->ep_desc[0].bEndpointAddress;
			HUB_Handle->InPipe  =  usbh_pipe_allocate (phost->data, HUB_Handle->InEp);
			usbh_pipe_create (phost->data,
                                  &phost->dev_prop, 
                                  HUB_Handle->InPipe,
                                  USB_EPTYPE_INTR,
                                  HUB_Handle->length);

      usbh_pipe_toggle_set(phost->data, HUB_Handle->InPipe, 0U);
		}
		 phost->hub = 1;
		 phost->active_class->class_data = (void *)&HUB_Handle;
	   status = USBH_OK;
	}
	
	return status;
}

static void USBH_HUB_InterfaceDeInit (usbh_host *phost ) {
	printf("USBH_HUB_InterfaceDeInit %d", phost->address);
	HUB_HandleTypeDef *HUB_Handle = phost->USBH_ClassTypeDef_pData[0];
	if(HUB_Handle->InPipe != 0x00) {		
		 usb_pipe_halt (phost->data, HUB_Handle->InPipe);
     usbh_pipe_free (phost->data, HUB_Handle->InPipe);
     HUB_Handle->InPipe = 0U;     /* reset the pipe as free */
	}
	
	if(phost->USBH_ClassTypeDef_pData[0]) {
		phost->active_class->class_data = NULL;
		free(phost->USBH_ClassTypeDef_pData[0]);
	}

}

static usbh_status USBH_HUB_ClassRequest(usbh_host *phost) {
	usbh_status status = USBH_BUSY;
	HUB_HandleTypeDef *HUB_Handle = phost->USBH_ClassTypeDef_pData[0];
	
	static HUB_CtlStateTypeDef bState = HUB_REQ_IDLE;
	
	if (bState != HUB_Handle->ctl_state) {
		printf("hub ctl state:%d\r\n",HUB_Handle->ctl_state);
	}
	bState = HUB_Handle->ctl_state;
	
  static uint8_t  port = 1;
	switch (HUB_Handle->ctl_state){
		case HUB_REQ_IDLE:
    case HUB_REQ_GET_DESCRIPTOR:
			if (USBH_OK == get_hub_descriptor (phost)) {
            usbh_hub_parse(&HUB_Handle->hub_desc, phost->dev_prop.data);
            //HUB_Handle->ctl_state = HUB_REQ_SET_POWER;
						HUB_Handle->ctl_state = HUB_REQ_DONE;
        }
			break;
		case HUB_REQ_SET_POWER:
			if(set_hub_port_power(phost, port) == USBH_OK){
    			// Reach last port
				printf("hub port power ok\r\n");
    			if(HUB_NumPorts == port)
    				HUB_Handle->ctl_state = HUB_WAIT_PWRGOOD;
    			else
    				port++;
    		} else {printf("hub port power fail\r\n");}
			break;
		case HUB_WAIT_PWRGOOD:
			usb_udelay(HUB_PwrGood);
			HUB_Handle->ctl_state = HUB_REQ_DONE;
			break;
		case HUB_REQ_DONE:
			phost->busy = 0;
			printf("%d HUB PORTS ENABLED", HUB_NumPorts);
			status = USBH_OK;
			break;
		}
    return status;
}

static void print_hub_state(HUB_StateTypeDef state) {

	switch(state) {
			case HUB_IDLE:
				printf("HUB_IDLE\r\n");
				break;
			case HUB_SYNC:
				printf("HUB_SYNC\r\n");
				break;
			case HUB_GET_DATA:
				printf("HUB_GET_DATA\r\n");
				break;
			case HUB_POLL:
				printf("HUB_POLL\r\n");
				break;
			case HUB_LOOP_PORT_CHANGED:
				printf("HUB_LOOP_PORT_CHANGED\r\n");
				break;
			case HUB_PORT_CHANGED:
				printf("HUB_PORT_CHANGED\r\n");
				break;
			case HUB_C_PORT_SUSPEND:
				printf("HUB_C_PORT_SUSPEND\r\n");
				break;
			case HUB_C_PORT_OVER_CURRENT:
				printf("HUB_C_PORT_OVER_CURRENT\r\n");
				break;
			case HUB_C_PORT_CONNECTION:
				printf("HUB_C_PORT_CONNECTION\r\n");
				break;
			case HUB_C_PORT_RESET:
				printf("HUB_C_PORT_RESET\r\n");
				break;
			case HUB_RESET_DEVICE:
				printf("HUB_RESET_DEVICE\r\n");
				break;
			case HUB_DEV_ATTACHED:
				printf("HUB_DEV_ATTACHED\r\n");
				break;
			case HUB_DEV_DETACHED:
				printf("HUB_DEV_DETACHED\r\n");
				break;
			case HUB_LOOP_PORT_WAIT:
				printf("HUB_LOOP_PORT_WAIT\r\n");
				break;
			case HUB_ERROR:
				printf("HUB_ERROR\r\n");
				break;
    	default:
				printf("HUB:default\r\n");
    		break;
	}
}


static usbh_status USBH_HUB_Process(usbh_host *phost) {
	usbh_status status = USBH_OK;
	HUB_HandleTypeDef *HUB_Handle =  (HUB_HandleTypeDef *)phost->USBH_ClassTypeDef_pData[0];
	static HUB_StateTypeDef tState = HUB_ERROR;
	if (tState != HUB_Handle->state) {
		print_hub_state(HUB_Handle->state);
	}
	tState = HUB_Handle->state;
	switch(HUB_Handle->state) {
			case HUB_IDLE:
				HUB_CurPort = 0;
     		HUB_Handle->state = HUB_SYNC;
				break;
			case HUB_SYNC:
				 if (1 == usb_frame_even(phost->data)) {
            HUB_Handle->state = HUB_GET_DATA;
        }
				break;
			case HUB_GET_DATA:
				
				if(hUSBHost[1].busy) break;
				usbh_data_recev (phost->data, HUB_Handle->buffer, HUB_Handle->InPipe, HUB_Handle->length);
			  HUB_Handle->state = HUB_POLL;
    	  HUB_Handle->timer =usb_curframe_get(phost->data);;
    	  HUB_Handle->DataReady = 0;
				break;
			case HUB_POLL:
				if (URB_DONE == usbh_urbstate_get (phost->data, HUB_Handle->InPipe)) {
            if (0U == HUB_Handle->DataReady) {
              HUB_Handle->DataReady = 1U;
							if(port_changed(HUB_Handle->buffer))
    		        	HUB_Handle->state = HUB_LOOP_PORT_CHANGED;
    		        else
    		        	HUB_Handle->state = HUB_GET_DATA;
                
            }
        } else {
            /* check IN endpoint stall status */
            if (URB_STALL == usbh_urbstate_get (phost->data,  HUB_Handle->InPipe)) {
                /* issue clear feature on interrupt in endpoint */ 
                if (USBH_OK == (usbh_clrfeature (phost, HUB_Handle->ep_addr, HUB_Handle->InPipe))) {
                    /* change state to issue next in token */
                    HUB_Handle->state = HUB_GET_DATA;
                }
            }
        }
				break;
			case HUB_LOOP_PORT_CHANGED:
				HUB_CurPort = get_port_changed();
        if(HUB_CurPort > 0) {
					phost->busy = 1;
					clear_port_changed(HUB_CurPort);
        	HUB_Handle->state = HUB_PORT_CHANGED;
        } else {
        		HUB_Handle->state = HUB_IDLE;
				}
				break;
			case HUB_PORT_CHANGED:
				if(get_hub_request(phost, USB_REQUEST_GET_STATUS, HUB_FEAT_SEL_PORT_CONN, HUB_CurPort, HUB_Handle->buffer, sizeof(USB_HUB_PORT_STATUS)) == USBH_OK) {
    			HUB_ChangeInfo = (USB_HUB_PORT_STATUS *)HUB_Handle->buffer;
					debug_port(HUB_Handle->buffer, HUB_ChangeInfo);
					if(HUB_ChangeInfo->wPortStatus.PORT_POWER) {
						if(HUB_ChangeInfo->wPortChange.C_PORT_OVER_CURRENT){
							HUB_Handle->state = HUB_C_PORT_OVER_CURRENT;
							break;
						}
						if(HUB_ChangeInfo->wPortChange.C_PORT_SUSPEND){
							HUB_Handle->state = HUB_C_PORT_SUSPEND;
							break;
						}
            if(HUB_ChangeInfo->wPortChange.C_PORT_CONNECTION){
              HUB_Handle->state = HUB_C_PORT_CONNECTION;
            } else {
							if(HUB_ChangeInfo->wPortStatus.PORT_CONNECTION) {
								if(HUB_ChangeInfo->wPortStatus.PORT_RESET) {
                  HUB_Handle->state = HUB_PORT_CHANGED;
								} else if(HUB_ChangeInfo->wPortChange.C_PORT_RESET) {
									HUB_Handle->state = HUB_C_PORT_RESET;
								} else if(HUB_ChangeInfo->wPortStatus.PORT_ENABLE) {
									HUB_Handle->state = HUB_DEV_ATTACHED;
								} else {
									HUB_Handle->state = HUB_RESET_DEVICE;
								}
              } else {
								HUB_Handle->state = HUB_DEV_DETACHED;
              }
            }
          }
    		}
				break;
			case HUB_C_PORT_SUSPEND:
				if(clear_port_feature(phost, HUB_FEAT_SEL_C_PORT_SUSPEND, HUB_CurPort) == USBH_OK) {
					HUB_Handle->state = HUB_PORT_CHANGED;
        }
				break;
			case HUB_C_PORT_OVER_CURRENT:
				if(clear_port_feature(phost, HUB_FEAT_SEL_C_PORT_OVER_CURRENT, HUB_CurPort) == USBH_OK) {
           HUB_Handle->state = HUB_PORT_CHANGED;
        }
				break;
			case HUB_C_PORT_CONNECTION:
				if(clear_port_feature(phost, HUB_FEAT_SEL_C_PORT_CONNECTION, HUB_CurPort) == USBH_OK) {
					HUB_Handle->state = HUB_PORT_CHANGED;
        }
				break;
			case HUB_C_PORT_RESET:
				if(clear_port_feature(phost, HUB_FEAT_SEL_C_PORT_RESET, HUB_CurPort) == USBH_OK) {
					HUB_Handle->state = HUB_PORT_CHANGED;
				}
				break;
			case HUB_RESET_DEVICE:
				if(set_port_feature(phost, HUB_FEAT_SEL_PORT_RESET, HUB_CurPort) == USBH_OK) {
					usb_udelay(150);
					HUB_Handle->state = HUB_PORT_CHANGED;
        }
				break;
			case HUB_DEV_ATTACHED:
				HUB_Handle->state = HUB_LOOP_PORT_WAIT;
				attach(phost, HUB_CurPort, HUB_ChangeInfo->wPortStatus.PORT_LOW_SPEED);
				phost->busy = 0;
				break;
			case HUB_DEV_DETACHED:
				HUB_Handle->state = HUB_LOOP_PORT_WAIT;
				detach(phost, HUB_CurPort);
				phost->busy = 0;
				break;
			case HUB_LOOP_PORT_WAIT:
				usb_udelay(10);
				HUB_Handle->state = HUB_LOOP_PORT_CHANGED;
				break;
			case HUB_ERROR:
    	default:
    		break;
	}
	
	return status;
}

static usbh_status USBH_HUB_SOFProcess(usbh_host *phost) {
	HUB_HandleTypeDef *HUB_Handle = (HUB_HandleTypeDef *)phost->USBH_ClassTypeDef_pData[0];
	if(HUB_Handle->state == HUB_POLL) {
		uint32_t frame_count = usb_curframe_get (phost->data);
		if ((frame_count > HUB_Handle->timer) && ((frame_count - HUB_Handle->timer) >= HUB_Handle->poll)) {
      HUB_Handle->state = HUB_GET_DATA;
    } else if ((frame_count < HUB_Handle->timer) && ((frame_count + 0x3FFFU - HUB_Handle->timer) >= HUB_Handle->poll)) {
      HUB_Handle->state = HUB_GET_DATA;
    }
	}
	return USBH_OK;
}

static void usbh_hub_parse (USB_HUB_DESCRIPTOR *printer_desc, uint8_t *buf) {
	USB_HUB_DESCRIPTOR  *HUB_Desc = (USB_HUB_DESCRIPTOR *) buf;
	HUB_NumPorts = (HUB_Desc->bNbrPorts > MAX_HUB_PORTS) ? MAX_HUB_PORTS : HUB_Desc->bNbrPorts;
	HUB_PwrGood  = (HUB_Desc->bPwrOn2PwrGood * 2);
  printer_desc = HUB_Desc;
}

static usbh_status get_hub_descriptor(usbh_host *phost) {
	usbh_status status = USBH_BUSY;
	if (CTL_IDLE == phost->control.ctl_state) {
        phost->control.setup.req = (usb_req) {
            .bmRequestType = USB_TRX_IN | USB_RECPTYPE_DEV | USB_REQTYPE_CLASS,
            .bRequest      = USB_GET_DESCRIPTOR,
            .wValue        = USBH_DESC(USB_DESCRIPTOR_HUB),
            .wIndex        = 0U,
            .wLength       = sizeof(USB_HUB_DESCRIPTOR)
        };

        usbh_ctlstate_config (phost, phost->dev_prop.data, sizeof(USB_HUB_DESCRIPTOR));
    }

    status = usbh_ctl_handler (phost);
	return status;
}

static usbh_status hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t dataDirection, uint8_t porta, uint8_t *buffer, uint16_t size) {
	usbh_status status = USBH_BUSY;
	uint8_t bmRequestType = (dataDirection == USB_DEVICE_REQUEST_GET) ? USB_TRX_IN : USB_TRX_OUT;
	
  phost->control.setup.req = (usb_req) {
    .bmRequestType = bmRequestType | USB_RECPTYPE_MASK | USB_REQTYPE_CLASS,
		.bRequest      = request,
		.wValue        = feature,
		.wIndex        = porta,
		.wLength       = size
  };

  usbh_ctlstate_config (phost, buffer, size);
  status = usbh_ctl_handler (phost);
	return status;
}

static usbh_status set_hub_port_power(usbh_host *phost, uint8_t hub_port)
{
	return hub_request(phost, USB_REQ_SET_FEATURE, HUB_FEATURE_SEL_PORT_POWER, USB_DEVICE_REQUEST_SET, hub_port, 0, 0);
}

static usbh_status get_hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t porta, uint8_t *buffer, uint16_t size)
{
    return hub_request(phost, request, feature, USB_DEVICE_REQUEST_GET, porta, buffer, size);
}

static usbh_status set_hub_request(usbh_host *phost, uint8_t request, uint8_t feature, uint8_t port)
{
    return hub_request(phost, request, feature, USB_DEVICE_REQUEST_SET, port, 0, 0);
}

static usbh_status clear_port_feature(usbh_host *phost, uint8_t feature, uint8_t port)
{
    return set_hub_request(phost, USB_REQUEST_CLEAR_FEATURE, feature, port);
}

static usbh_status set_port_feature(usbh_host *phost, uint8_t feature, uint8_t port)
{
    return set_hub_request(phost, USB_REQUEST_SET_FEATURE, feature, port);
}

static void clear_port_changed(uint8_t port)
{
	switch(port)
	{
		case 1:	HUB_Change.bPorts.PORT_1 = 0; break;
		case 2: HUB_Change.bPorts.PORT_2 = 0; break;
		case 3: HUB_Change.bPorts.PORT_3 = 0; break;
		case 4: HUB_Change.bPorts.PORT_4 = 0; break;
	}
}

static uint8_t get_port_changed()
{
	if(HUB_Change.bPorts.PORT_1)	return 1;
	if(HUB_Change.bPorts.PORT_2)	return 2;
	if(HUB_Change.bPorts.PORT_3)	return 3;
	if(HUB_Change.bPorts.PORT_4)	return 4;

	return 0;
}

static uint8_t port_changed(uint8_t *b)
{
    if(b[0] != 0x00)
    {
    	HUB_Change.val = 0x00;

        if(b[0] & (1<<1)) { HUB_Change.bPorts.PORT_1 = 1; }
        if(b[0] & (1<<2)) { HUB_Change.bPorts.PORT_2 = 1; }
        if(b[0] & (1<<3)) { HUB_Change.bPorts.PORT_3 = 1; }
        if(b[0] & (1<<4)) { HUB_Change.bPorts.PORT_4 = 1; }
        return HUB_Change.val > 0;
    }

    return 0;
}

void detach(usbh_host *_phost, uint16_t idx) {
	usbh_host *pphost = &hUSBHost[idx];
	if(pphost->valid) {
		
		printf("detach %d", pphost->address);
		if(pphost->usr_cb != NULL && pphost->usr_cb->dev_detach != NULL) {
			pphost->usr_cb->dev_detach();
		}

	  if(pphost->active_class != NULL) {
	    pphost->active_class->class_deinit(pphost);
	    pphost->active_class = NULL;
	  }

		pphost->address	    = 0;
		pphost->busy  	    = 0;
		pphost->class_num 	= 0;
		pphost->valid 	    = 0;

		uint32_t i;
		for(i = 0; i< USBH_DATA_BUF_MAX_LEN; i++) {
			pphost->dev_prop.data[i] = 0;
		}

		for(i = 0; i < pphost->interfaces; ++i) {
			free(pphost->USBH_ClassTypeDef_pData[i]);
			pphost->USBH_ClassTypeDef_pData[i] = NULL;
		}

		pphost->interfaces = 0;
		usb_core_driver *udev = (usb_core_driver *)pphost->data;
    udev->host.connect_status = 0U;

	}
}
#include "usbh_usr.h"
static void attach(usbh_host *phost, uint16_t idx, uint8_t lowspeed) {
	usbh_host *pphost = &hUSBHost[idx];
	usb_core_driver *ppcore = &hUSBCore[idx];
	printf("attach %d", idx);
	if(pphost->valid) {
		printf("ATTACH ERROR, ALREADY ATTACHED");
		detach(pphost, idx);
	}
	usbh_init(pphost,ppcore,USB_CORE_ENUM_HS,&usr_cb);
	
}

static void debug_port(uint8_t *buff, __IO USB_HUB_PORT_STATUS *info)
{
#if 0
    LOG1("PORT STATUS [%02X %02X %02X %02X] ", buff[0], buff[1], buff[2], buff[3]);

    if(info->wPortStatus.PORT_CONNECTION)       LOG1("PORT_CONNECTION ");
    if(info->wPortStatus.PORT_ENABLE)           LOG1("PORT_ENABLE ");
    if(info->wPortStatus.PORT_SUSPEND)          LOG1("PORT_SUSPEND ");
    if(info->wPortStatus.PORT_OVER_CURRENT)     LOG1("PORT_OVER_CURRENT ");
    if(info->wPortStatus.PORT_RESET)            LOG1("PORT_RESET ");
    if(info->wPortStatus.PORT_POWER)            LOG1("PORT_POWER ");
    if(info->wPortStatus.PORT_LOW_SPEED)        LOG1("PORT_LOW_SPEED ");
    if(info->wPortStatus.PORT_HIGH_SPEED)       LOG1("PORT_HIGH_SPEED ");
    if(info->wPortStatus.PORT_TEST)             LOG1("PORT_TEST ");
    if(info->wPortStatus.PORT_INDICATOR)        LOG1("PORT_INDICATOR ");

    if(info->wPortChange.C_PORT_CONNECTION)     LOG1("C_PORT_CONNECTION ");
    if(info->wPortChange.C_PORT_ENABLE)         LOG1("C_PORT_ENABLE ");
    if(info->wPortChange.C_PORT_SUSPEND)        LOG1("C_PORT_SUSPEND ");
    if(info->wPortChange.C_PORT_OVER_CURRENT)   LOG1("C_PORT_OVER_CURRENT ");
    if(info->wPortChange.C_PORT_RESET)          LOG1("C_PORT_RESET ");
    LOG1("\r\n");
#endif
}
